import base64
import json
import os
import time
import traceback
import typing
import numpy
import uuid
from yiqiu_core.Chess import Chess
from yiqiu_core.Rule import Rule, Rule_Dict, CheckStatusUsingRule
from yiqiu_core.Position import ChessPosition
import collections


ChessBoardStatus = collections.namedtuple(
    'ChessBoardStatus', ['board', 'next_chess_type'])
# 例如：c = ChessBoardStatus(chess_board.GetBoardStatus(),Chess.Black)


class ChessBoard(object):
    def __init__(self, row: int, col: int) -> None:
        if not isinstance(row, int) or not isinstance(col, int):
            raise Exception("'row' and 'col' should be integer larger than 0.")
        if row <= 0 or col <= 0:
            raise Exception("'row' and 'col' should be integer larger than 0.")

        self.row_cnt = row
        self.col_cnt = col
        self.chessboard_array = numpy.zeros(
            self.row_cnt*self.col_cnt, dtype=int)
        self.chessboard_array = self.chessboard_array.reshape(
            self.row_cnt, self.col_cnt)

        # print(self.chessboard_array)
        for i in range(self.row_cnt):
            for j in range(self.col_cnt):
                self.chessboard_array[i][j] = Chess.Empty

    def SetBoardStatus(self, chessboard_array: numpy.ndarray):
        if not chessboard_array.shape == (self.row_cnt, self.col_cnt):
            raise Exception("not the same shape to original chessboard!")
        self.chessboard_array = chessboard_array

    def GetBoardStatus(self):
        return self.chessboard_array


class GameMemento:
    def __init__(
        self,
        rule_name: str,
        history: typing.List[ChessBoardStatus],
        board_row_num: int,
        board_col_num: int,
        chess_order: typing.List,
        description: str = None,
        skip_init: bool = False
    ):
        if skip_init:
            # 跳过初始化，后续通过 Load 函数进行初始化，适用于 Serialize 对象初始化
            return
        self.rule_name = rule_name
        if rule_name not in Rule_Dict.keys():
            raise Exception('unsupported rule_name, [%s]' % rule_name)
        self.rule_selected = Rule_Dict[rule_name]
        self.history = history
        self.board_row_num = board_row_num
        self.board_col_num = board_col_num
        self.chess_order = chess_order
        self.description = description

    def GetDescription(self):
        return self.description

    def GetState(self):
        final_state = {
            "rule_name": self.rule_name,
            "rule_selected": self.rule_selected,
            "history": self.history,
            "board_row_num": self.board_row_num,
            "board_col_num": self.board_col_num,
            "chess_order": self.chess_order,
        }
        if self.description is not None:
            final_state["description"] = self.description
        return final_state

    def Serialize(self):
        """返回一个可以被 json 合法 encode 的字典对象"""
        history = []
        for h in self.history:
            history.append(
                {'board': h.board.tolist(), 'next_chess_type': h.next_chess_type})
        return {
            "rule_name": self.rule_name,
            "history": history,
            "board_row_num": self.board_row_num,
            "board_col_num": self.board_col_num,
            "chess_order": self.chess_order,
            "description": self.description,
        }

    def Load(self, status: typing.Dict):
        """Serialize 的逆操作，可以装载其他 Memento Serialize 出的数据结构"""
        self.rule_name = status["rule_name"]
        if self.rule_name not in Rule_Dict.keys():
            raise Exception('unsupported rule_name, [%s]' % self.rule_name)
        self.rule_selected = Rule_Dict[self.rule_name]
        self.history = []
        for h in status["history"]:
            self.history.append(ChessBoardStatus(
                numpy.asarray(h['board']), h['next_chess_type']))
        self.board_row_num = status["board_row_num"]
        self.board_col_num = status["board_col_num"]
        self.chess_order = status["chess_order"]
        self.description = status["description"]


class Game(object):
    def __init__(self, chess_board: ChessBoard, rule_name: str, game_uuid=None):
        """
            rule_name 参考 Rule_Dict 中的键值
        """
        if game_uuid is None:
            self.game_uuid = uuid.uuid1()
        else:
            self.game_uuid = game_uuid
        self.chess_order = [Chess.Black, Chess.White]  # 行棋顺序
        self.current_step = 0  # 存储到目前为止，共走了多少步，可通过该变量配合 self.chess_order 来推断下一步轮到哪一方行棋
        self.chess_board = chess_board

        if rule_name not in Rule_Dict.keys():
            raise Exception('unsupported rule_name, [%s]' % rule_name)
        self.rule_name = rule_name
        self.rule_selected = Rule_Dict[rule_name]

        # 棋局历史是包含当前状态的，每一个棋局状态由两部分组成，第一部分是当前棋盘数组，第二部分是下一步由谁行棋
        self.history = [
            ChessBoardStatus(chess_board.GetBoardStatus().copy(),
                             self._defer_next_chesstype())
        ]

    def _defer_next_chesstype(self):
        # 推断下一步该哪一方行棋
        return self.chess_order[self.current_step % len(self.chess_order)]

    # 悔棋
    def Regret(self):
        if self.current_step == 0:
            return False, "初始局面，无法继续悔棋"
        else:
            self.current_step -= 1
            self.history.pop()
            self.chess_board.SetBoardStatus(
                self.history[-1].board
            )
            return True, ""

    def PlaceChess(self, p: ChessPosition):
        legal_move, winner = True, None
        chesstype_to_move = self._defer_next_chesstype()
        status, new_board = self.rule_selected.CheckBoard(
            self.chess_board.GetBoardStatus(),
            p,
            chesstype_to_move,
        )
        # print(status, new_board)
        if status == CheckStatusUsingRule.CanNotPlace:
            legal_move = False
        else:
            self.chess_board.SetBoardStatus(new_board)
            self.current_step += 1
            self.history.append(
                ChessBoardStatus(
                    self.chess_board.GetBoardStatus().copy(),
                    self._defer_next_chesstype(),
                )
            )

            if status == CheckStatusUsingRule.Success_and_Continue:
                pass
            elif status == CheckStatusUsingRule.Success_and_Lose:
                winner = self._defer_next_chesstype()
            elif status == CheckStatusUsingRule.Success_and_Tie:
                winner = Chess.Empty
            elif status == CheckStatusUsingRule.Success_and_Win:
                winner = chesstype_to_move
            else:
                raise Exception('illegal status!')
        return legal_move, winner

    def CreateMemento(self, description=None):
        # description 为对本次存档的描述
        return GameMemento(
            rule_name=self.rule_name,
            history=self.history,
            board_row_num=self.chess_board.GetBoardStatus().shape[0],
            board_col_num=self.chess_board.GetBoardStatus().shape[1],
            chess_order=self.chess_order,
            description=description
        )

    def RestoreMemento(self, m: GameMemento):
        if not isinstance(m, GameMemento):
            raise Exception(
                "parameter m should be an instance of GameMemento.")
        m_state = m.GetState()
        self.rule_name = m_state["rule_name"]
        self.rule_selected = m_state["rule_selected"]
        self.history = m_state["history"]
        self.current_step = len(self.history) - 1
        self.chess_order = m_state["chess_order"]
        board_shape = self.history[0].board.shape
        self.chess_board = ChessBoard(board_shape[0], board_shape[1])
        self.chess_board.SetBoardStatus(self.history[-1].board)


class GameCaretaker(object):
    """
    存档文件中所存储的数据最终经过解码后
    """

    def __init__(self):
        self.active_games: typing.Dict[str, Game] = {}  # 键为 uuid
        self.mementos: typing.List[GameMemento] = []
        if not os.path.exists('game_archive'):
            os.makedirs('game_archive')
        all_archive_files = os.listdir('game_archive')
        print(all_archive_files)
        for file in all_archive_files:
            fopen = open('game_archive/%s' % file, 'r')
            f_str = ''
            for eachLine in fopen:
                f_str += eachLine
            fopen.close()
            try:
                self.mementos.append(self._decode_file_to_memento(f_str))
            except Exception:
                # import traceback
                print(traceback.format_exc())
                print('无效的存档文件 -- %s' % file)

    # 本函数可能会抛出异常，可能 file_data 经过解码后不符合我们预设的格式
    def _decode_file_to_memento(self, file_data):
        state_dict = json.loads(base64.b64decode(file_data))
        m = GameMemento('', [], 0, 0, [], None, skip_init=True)
        m.Load(state_dict)
        return m

    def _archive_memento(self, m: GameMemento):
        print(m.Serialize())
        # m should be an instance of GameMemento
        data = base64.b64encode(
            json.dumps(
                m.Serialize()
            ).encode('utf-8')
        ).decode('utf-8')
        file_name = 'game_archive/%d' % int(time.time())
        f = open(file_name, 'w')
        f.write(data)
        f.close()

    def CreateMemento(self, game_uuid: str, description: str):
        # description 为存档描述，此处必须传入，用以区分不同存档
        if not isinstance(description, str):
            raise Exception("func CreateMemento need a description of str")
        if game_uuid not in self.active_games.keys():
            raise Exception("cant find a game with uuid [%s]" % game_uuid)
        memento = self.active_games[game_uuid].CreateMemento(description)
        self.mementos.append(memento)
        self._archive_memento(memento)

    def ShowMementos(self):
        return [self.mementos[i].GetDescription() for i in range(len(self.mementos))]
        # print('----------------------------')
        # print('当前存档展示：')
        # for i in range(len(self.mementos)):
        #     print('index:[%d],description:[%s]' %
        #           (i, self.mementos[i].GetDescription()))
        # print('----------------------------')

    def RestoreMementos(self, game_uuid: str, m_index: int):
        if not isinstance(m_index, int):
            raise Exception("m_index should be integer")
        if m_index < 0 or m_index >= len(self.mementos):
            raise Exception("m_index with an illegal value")
        if game_uuid not in self.active_games.keys():
            raise Exception("cant find a game with uuid [%s]" % game_uuid)
        self.active_games[game_uuid].RestoreMemento(self.mementos[m_index])

    def CreateGame(self, row_size: int, col_size: int, rule_name: str):
        new_game_uuid = ''
        try:
            new_game = Game(ChessBoard(row_size, col_size), rule_name)
        except Exception as e:
            print(traceback.format_exc())
            return False, new_game_uuid, str(e)
        while new_game_uuid == '' or new_game_uuid in self.active_games.keys():
            new_game_uuid = str(uuid.uuid1())
        self.active_games[new_game_uuid] = new_game
        return True, new_game_uuid, '创建对局成功，当前对局 ID 为 [%s]' % new_game_uuid
